package apibasej.basic.misc.standalone;

import java.math.BigInteger;


public class UtilMathBase {

	/*
	public static void main(String[] args) {
		
		BigInteger b = new BigInteger(new byte[]{-127});
		System.out.println(b.signum());
		System.out.println( b.toByteArray()[0] );
		System.out.println(  b.toString(2) );
		System.out.println( Integer.toBinaryString(b.toByteArray()[0]) );
		
		System.out.println(Integer.toBinaryString(127));
		System.out.println(Integer.toBinaryString(-127));
		System.out.println(Integer.toBinaryString(-1));
		System.out.println(Integer.toBinaryString(0));
		
		UtilMathBase u = new UtilMathBase(Base.BIN);
		System.out.println(u.decimalToOther(127));
		
		BigInteger b1 = new BigInteger(1,new byte[]{-128});
		System.out.println( UtilString.completeZerosLeft( b1.toString(2), 8) );
		System.out.println(b1.toByteArray()[0]+","+b1.toByteArray()[1] );
		
		// tipos com sinal são negativos quando começam com 1 em binário porque começam a contar a partir da metade
		// EX: maior byte é 127 = 01111111, a partir daí vê como negativo 128 = 10000000, se começa com 1 é negativo
		
		// HEX e BIN terá sempre uma quantidade fixa de carcteres para representar cada byte
		// para HEX(16) 2 e para BIN(2) 8 ... portanto cada byte pode ser codificado separadamente ... 
		
		//estudar complemento de 2 para entender negativos ...
		//os negativos são o inverso do binário positivo mais 1
	}*/
	
	public enum Base{BIN,OCT,HEX,ALPHABET_UPPER,NUMBER_ALPHABET_UPPER,NUMBER_ALPHABET_UPPER_AND_LOWER}
	

	public UtilMathBase(Base base) {
		if(Base.BIN.equals(base)) {
			repr = new char[]{'0','1'};
		}else if(Base.OCT.equals(base)) {
			repr = new char[]{'0','1','2','3','4','5','6','7'};
		}else if(Base.HEX.equals(base)) {
			caseSensitive = false;
			repr = new char[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
		}else if(Base.ALPHABET_UPPER.equals(base)) {
			caseSensitive = false;
			repr = new char[]{'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
		}else if(Base.NUMBER_ALPHABET_UPPER.equals(base)) {
			caseSensitive = false;
			repr = new char[]{'0','1','2','3','4','5','6','7','8','9',
							  'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
		}else if(Base.NUMBER_ALPHABET_UPPER_AND_LOWER.equals(base)) {
			caseSensitive = true;
			repr = new char[]{'0','1','2','3','4','5','6','7','8','9',
							  'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
							  'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
		}else{// não deve ocorrer
			throw new IllegalArgumentException("Invalid base: "+base);
		}
	}
	
	// VALIDAR PARA NÃO ACEITAR NEGATIVOS !!! CRIAR FUNCOES QUE RECEBEM BigInteger
	
	private char charIndicatorNegative = '-';
	private char[] repr;
	private boolean caseSensitive = true;
	
	// LEMBRAR DE PREENCHER ZEROS A ESQUERDA SE FOR CONVERTER PARA HEX OU OUTRAS REPRESENTAÇÕES PARA byte[]
	
	// CUIDADO PARA NÃO TER O CHAR INDICADOR DE NAGATIVO NOS CHARS DA REPRESENTAÇÃO !
	public UtilMathBase(char... repr) {
		this.repr = repr;
	}
	
	public String decimalToOther(byte[] decimal){
		return decimalToOther(new BigInteger(1,decimal));// tem que ser BigInteger com o 1, indicando que é positivo, para o BigInteger tratar cada byte como ele realmente é
	}
	public String decimalToOther(long decimal){
		return decimalToOther(BigInteger.valueOf(decimal));
	}
	public String decimalToOther(BigInteger decimal){
		BigInteger qtdDig = BigInteger.valueOf(repr.length); // só transforma para BigInteger para as operações que serão realizadas
		StringBuilder r = new StringBuilder();
		boolean isNegative = decimal.signum()<0;
		if(isNegative){
			decimal = decimal.negate();// se for negativo, representa como positivo e adiciona o sinal de negativo na frente
		}
		while(decimal.compareTo(qtdDig)>=0){ // decimal >= qtdDig
			BigInteger resto = decimal.mod(qtdDig);
			decimal = decimal.subtract(resto).divide(qtdDig); //(decimal-resto)/qtdDig; // decimal-resto para dar divisão exata (poderia fazer cast ...)
			r.insert(0,repr[resto.intValue()]);//insere o caractere a esquerda, o resto desta divisão sempre caberá em um int, pois a qtdDig é um número pequeno //decimal%qtdDig;
			//System.out.println(">"+resto+" - "+decimal);
		}
		r.insert(0,repr[decimal.intValue()]); // só chega aqui se o decimal for menor que a quantidade de caracteres de representação 
		if(isNegative) r.insert(0,getCharIndicatorNegative());
		return r.toString();
	}
	
	// transforma outra representação qualquer para decimal
	public BigInteger otherToDecimal(String other){
		BigInteger qtdDig = BigInteger.valueOf(repr.length); // só transforma para BigInteger para as operações que serão realizadas
		char[] n = (isCaseSensitive()?other:other.toLowerCase()).toCharArray(); // deixar case sensitive !!!!
		int firstPos = n[0]==getCharIndicatorNegative()?1:0;
		BigInteger decimal = BigInteger.valueOf(0);
		for(int i=firstPos;i<n.length;i++){
			char d = n[i];
			BigInteger valCasa = qtdDig.pow(n.length-i-1);// valor correspondente de acordo com a posição do número (Math.pow(qtdDig,n.length-i-1) )
			BigInteger valThisChar = BigInteger.valueOf( getDecimal(d) );// o valor int que o caractere representa
			BigInteger valCharCasa = valCasa.multiply(valThisChar);// o valor correspondente ao caractere de acordo com a casa que ele está
			decimal = decimal.add( valCharCasa ); 
		}
		return n[0]==getCharIndicatorNegative()?decimal.negate():decimal;
	}
	public byte[] otherToDecimalInBytes(String other){
		// Lembrar que o BigInteger coloca um zero a esquerda se o primeiro byte for negativo (prim bit 1) e no criação do BigInteger foi indicado que ele é positivo com a flag 1 ou com a string que não começa com -
		return otherToDecimal(other).toByteArray();
	}
	private int getDecimal(char digit){
		for(char i=0;i<repr.length;i++){
			if(repr[i]==digit || (!isCaseSensitive() && Character.toLowerCase(repr[i])==Character.toLowerCase(digit))) {
				return i;
			}
		}
		throw new RuntimeException("Invalid digit: "+digit);
	}

	
	
	
	public char getCharIndicatorNegative() {
		return charIndicatorNegative;
	}

	public char[] getRepr() {
		return repr;
	}

	public void setCharIndicatorNegative(char charIndicatorNegative) {
		this.charIndicatorNegative = charIndicatorNegative;
	}

	public void setRepr(char[] repr) {
		this.repr = repr;
	}
	public boolean isCaseSensitive() {
		return caseSensitive;
	}
	public void setCaseSensitive(boolean caseSensitive) {
		this.caseSensitive = caseSensitive;
	}
	
}
